﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Pathfinding;


[RequireComponent(typeof(Seeker))]
[RequireComponent(typeof(SteveBrain))]
[RequireComponent(typeof(StevePathfindedController))]
public class Boid : MonoBehaviour {

    #region dichiarazioni
    private MovementState currentMovementState;

    public enum MovementState : int
    {
        IDLE,
        MOVING,
        ORGANIZING,
        WAITING
    }

    public MovementState CurrentMovementState
    {
        get
        {
            return currentMovementState;
        }
        set
        {
            currentMovementState = value;
        }
    }

    //The intensity in which Boids will be attracted to each other. For this example I am actually setting it to 0, since the pathfinding will handle most of the coherency.
    public float coherencyWeight = 1;

    //The intensity in which Boids will be repelled from each other.
    public float separationWeight = 1;

    public float maxSeparation = 3;

    //Scan area’s radius.
    public float radius = 2;

    //How many times per second to scan the area.
    public int pingsPerSecond = 10;

    public float PingFrequency
    {
        get
        {
            return (1 / pingsPerSecond);
        }
    }

    //Which layers to listen for when scanning.
    public LayerMask radarLayers;

    private List<Boid> neighbors = new List<Boid>();

    //variabili per l'attesa del boid prima del prossimo spostamento
    private float _timeSleep = 0;
    private float _sleep = 0;
    private Vector3 _nextPosition;

    //Path that is going to be followed by the Boid.
    private Path path;

    //The Boid’s current point on it’s path.
    private int currentWayPoint = 0;

    //The maximum distance from the Boid to the a waypoint for it to continue to the next waypoint.
    private float nextWayPoint = 1;
    
    //Bool to determine if you’ve reached the end of the path.
    private bool journeyComplete = true;

    private IEnumerator StartTick(float freq)
    {
        yield return new WaitForSeconds(freq);
        RadarScan();
    }
    #endregion 

    //Start sets up the references to the Seeker class and the Character Controller.
    //It also finds all the Boids in the scene and adds them to a List called ‘boids’ 
    // and sets the current movement state to IDLE.
	void Start () {
        CurrentMovementState = MovementState.IDLE;
	}


    public void startReacting(){
        SteveBrain agent = this.GetComponent<SteveBrain>();
        StevePathfindedController astar = GetComponent<StevePathfindedController>();
        this.StartCoroutine("StartTick", PingFrequency);

        astar.SetTarget(agent.party.destination, new EventDelegate(this, "OnEndPath"));
        currentMovementState = MovementState.MOVING;        //nessun controllo (primo click)
    }

    public void stopReacting() {
        SteveBrain agent = this.GetComponent<SteveBrain>();
        StevePathfindedController astar = GetComponent<StevePathfindedController>();
        this.StopCoroutine("StartTick");

        astar.Stop();
        this.CurrentMovementState = MovementState.IDLE;
    }

    
    private void EditTarget(Vector3 target, float sleep = 0){
        _timeSleep = 0;
        if ( sleep > 0){
            _sleep = sleep;
            _nextPosition = target;
            currentMovementState = MovementState.WAITING;
        }else{
            StevePathfindedController astar = GetComponent<StevePathfindedController>();
            astar.SetTarget(target, new EventDelegate(this, "OnEndPath"));
            currentMovementState = MovementState.MOVING;
        }
    }


    private void OnEndPath(){
        currentMovementState = MovementState.ORGANIZING; 
    }

    /// <summary>
    /// Corutine per effettuare la scansione dei vicini (solo quando il boid non è in movimento)
    /// </summary>
    private void RadarScan()
    {
        switch (currentMovementState)
        {
            case MovementState.IDLE:
                break;

            //nessun controllo sulla camminata
            case MovementState.MOVING:      
                break;

            //in questo stato controllo i boid vicini
            case MovementState.ORGANIZING:

                neighbors = GetNeighbors();
                Vector3 nextPosition = BoidBehaviors();
                
                //fa attendere il personaggio in maniera casuale per un massimo di 2 secondi
                float sleep = Random.value * 2;
                EditTarget(nextPosition, sleep);

                break;

            //per rendere le cose casuali, faccio attendere un pò il boid
            case MovementState.WAITING:
                _timeSleep += Time.deltaTime;
                if ( _timeSleep > _sleep ){
                    EditTarget(this._nextPosition, 0);
                }
                break;
            default:
                break;
        }

        //Start the timer again
        StartCoroutine("StartTick", PingFrequency);
    }


    /// <summary>
    /// Finds the neighbourhood of this boid.
    /// </summary>
    /// <returns></returns>
    private List<Boid> GetNeighbors(){
        List<Boid> neighbourhood = new List<Boid>();

        //Fills the detected array with the objects that were within the Physics.OverlapSphere area,
        //which is the Boids position and outstretched by the radius.
        Collider[] detected = Physics.OverlapSphere(transform.position, this.radius, this.radarLayers);
        foreach (Collider collider in detected){
            Boid boid = collider.GetComponent<Boid>();
            //If the collider is not that same gameObject as itself then add it to the list of neighbors
            if (boid != null & collider.gameObject != this.gameObject) {
                neighbourhood.Add(boid);
            }
        }

        return neighbourhood;
    }



    /// <summary>
    /// Calculates the forces for Coherency and Separation and returns 
    /// the vector to be used for the character controller.
    /// </summary>
    /// <returns></returns>
    private Vector3 BoidBehaviors(){
        //https://github.com/NStockwell/Boids/blob/master/MiniclipHack/Assets/SimpleBoids/Boid.cs
        SteveBrain agent = this.GetComponent<SteveBrain>();
        Vector3 center = agent.party.Center - transform.localPosition;	// cohesion
        Vector3 follow = agent.party.destination - transform.localPosition; // follow leader
        Vector3 separation = Vector3.zero;
        
        if (neighbors == null || neighbors.Count == 0)
        {
            //Debug.LogError("neighbors of " + agent.gameObject.name + " is null");    
        }
        
        foreach (Boid b in neighbors)
        {
            if (b != this)
            {
                Vector3 relativePos = transform.localPosition - b.transform.localPosition;
                separation += relativePos / (relativePos.sqrMagnitude);
            }
        }

        //evito una repulsione troppo forte dando un tetto massimo alla lunghezza del vettore
        if (separation.magnitude > maxSeparation)
            separation = separation.normalized * maxSeparation;

        //assegno una direzione casuale 
        Vector3 randomize = new Vector3(
            (Random.value * 2) - 1, (Random.value * 2) - 1, (Random.value * 2) - 1);// randomize

        randomize.Normalize();

        return (1 * center +
                separationWeight* separation +
                coherencyWeight * follow +
                1 * randomize + 
                transform.localPosition);
    }
}